a tool for shared writing and social publishing
298
fork

Configure Feed

Select the types of activity you want to include in your feed.

at update/reader 256 lines 7.1 kB view raw
1import { useReplicache } from "src/replicache"; 2import React, { useEffect, useState } from "react"; 3import { getShareLink } from "./getShareLink"; 4import { useEntitySetContext } from "components/EntitySetProvider"; 5import { useSmoker } from "components/Toast"; 6import { Menu, MenuItem } from "components/Menu"; 7import { ActionButton } from "components/ActionBar/ActionButton"; 8import useSWR from "swr"; 9import LoginForm from "app/login/LoginForm"; 10import { CustomDomainMenu } from "./DomainOptions"; 11import { useIdentityData } from "components/IdentityProvider"; 12import { 13 useLeafletDomains, 14 useLeafletPublicationData, 15} from "components/PageSWRDataProvider"; 16import { ShareSmall } from "components/Icons/ShareSmall"; 17import { getPublicationURL, getDocumentURL } from "app/lish/createPub/getPublicationURL"; 18import { AtUri } from "@atproto/syntax"; 19import { useIsMobile } from "src/hooks/isMobile"; 20 21export type ShareMenuStates = "default" | "login" | "domain"; 22 23export let useReadOnlyShareLink = () => { 24 let { permission_token, rootEntity } = useReplicache(); 25 let entity_set = useEntitySetContext(); 26 let { data: publishLink } = useSWR( 27 "publishLink-" + permission_token.id, 28 async () => { 29 if ( 30 !permission_token.permission_token_rights.find( 31 (s) => s.entity_set === entity_set.set && s.create_token, 32 ) 33 ) 34 return; 35 let shareLink = await getShareLink( 36 { id: permission_token.id, entity_set: entity_set.set }, 37 rootEntity, 38 ); 39 return shareLink?.id; 40 }, 41 ); 42 return publishLink; 43}; 44 45export function ShareOptions() { 46 let [menuState, setMenuState] = useState<ShareMenuStates>("default"); 47 let { data: pub } = useLeafletPublicationData(); 48 let isMobile = useIsMobile(); 49 50 return ( 51 <Menu 52 asChild 53 side={isMobile ? "top" : "right"} 54 align={isMobile ? "center" : "start"} 55 className="max-w-xs" 56 onOpenChange={() => { 57 setMenuState("default"); 58 }} 59 trigger={ 60 <ActionButton 61 icon=<ShareSmall /> 62 secondary 63 label={`Share ${pub ? "Draft" : ""}`} 64 /> 65 } 66 > 67 {menuState === "login" ? ( 68 <div className="px-3 py-1"> 69 <LoginForm text="Save your Leaflets and access them on multiple devices!" /> 70 </div> 71 ) : menuState === "domain" ? ( 72 <CustomDomainMenu setShareMenuState={setMenuState} /> 73 ) : ( 74 <ShareMenu 75 setMenuState={setMenuState} 76 domainConnected={false} 77 isPub={!!pub} 78 /> 79 )} 80 </Menu> 81 ); 82} 83 84const ShareMenu = (props: { 85 setMenuState: (state: ShareMenuStates) => void; 86 domainConnected: boolean; 87 isPub?: boolean; 88}) => { 89 let { permission_token } = useReplicache(); 90 let { data: pub, normalizedDocument } = useLeafletPublicationData(); 91 92 let postLink = 93 pub?.documents && normalizedDocument 94 ? getDocumentURL( 95 normalizedDocument, 96 pub.documents.uri, 97 pub?.publications || null, 98 ) 99 : null; 100 let publishLink = useReadOnlyShareLink(); 101 let [collabLink, setCollabLink] = useState<null | string>(null); 102 useEffect(() => { 103 // strip leading '/' character from pathname 104 setCollabLink(window.location.pathname.slice(1)); 105 }, []); 106 let { data: domains } = useLeafletDomains(); 107 108 return ( 109 <> 110 <ShareButton 111 text={`Share ${postLink ? "Draft" : ""} Edit Link`} 112 subtext="" 113 smokerText="Edit link copied!" 114 id="get-edit-link" 115 link={collabLink} 116 /> 117 <ShareButton 118 text={`Share ${postLink ? "Draft" : ""} View Link`} 119 subtext=<> 120 {domains?.[0] ? ( 121 <> 122 This Leaflet is published on{" "} 123 <span className="italic underline"> 124 {domains[0].domain} 125 {domains[0].route} 126 </span> 127 </> 128 ) : ( 129 "" 130 )} 131 </> 132 smokerText="View link copied!" 133 id="get-view-link" 134 fullLink={ 135 domains?.[0] 136 ? `https://${domains[0].domain}${domains[0].route}` 137 : undefined 138 } 139 link={publishLink || ""} 140 /> 141 {postLink && ( 142 <> 143 <hr className="border-border-light" /> 144 145 <ShareButton 146 text="Share Published Link" 147 subtext="" 148 smokerText="Post link copied!" 149 id="get-post-link" 150 fullLink={postLink.includes("http") ? postLink : undefined} 151 link={postLink} 152 /> 153 </> 154 )} 155 {!props.isPub && ( 156 <> 157 <hr className="border-border mt-1" /> 158 <DomainMenuItem setMenuState={props.setMenuState} /> 159 </> 160 )} 161 </> 162 ); 163}; 164 165export const ShareButton = (props: { 166 text: React.ReactNode; 167 subtext?: React.ReactNode; 168 smokerText: string; 169 id: string; 170 link: null | string; 171 fullLink?: string; 172 className?: string; 173}) => { 174 let smoker = useSmoker(); 175 176 return ( 177 <MenuItem 178 id={props.id} 179 onSelect={(e) => { 180 e.preventDefault(); 181 let rect = document.getElementById(props.id)?.getBoundingClientRect(); 182 if (props.link || props.fullLink) { 183 navigator.clipboard.writeText( 184 props.fullLink 185 ? props.fullLink 186 : `${location.protocol}//${location.host}/${props.link}`, 187 ); 188 smoker({ 189 position: { 190 x: rect ? rect.left + (rect.right - rect.left) / 2 : 0, 191 y: rect ? rect.top + 26 : 0, 192 }, 193 text: props.smokerText, 194 }); 195 } 196 }} 197 > 198 <div className={`group/${props.id} ${props.className} leading-snug`}> 199 {props.text} 200 201 {props.subtext && ( 202 <div className={`text-sm font-normal text-tertiary`}> 203 {props.subtext} 204 </div> 205 )} 206 </div> 207 </MenuItem> 208 ); 209}; 210 211const DomainMenuItem = (props: { 212 setMenuState: (state: ShareMenuStates) => void; 213}) => { 214 let { identity } = useIdentityData(); 215 let { data: domains } = useLeafletDomains(); 216 217 if (identity === null) 218 return ( 219 <div className="text-tertiary font-normal text-sm px-3 py-1"> 220 <button 221 className="text-accent-contrast hover:font-bold" 222 onClick={() => { 223 props.setMenuState("login"); 224 }} 225 > 226 Log In 227 </button>{" "} 228 to publish on a custom domain! 229 </div> 230 ); 231 else 232 return ( 233 <> 234 {domains?.[0] ? ( 235 <button 236 className="px-3 py-1 text-accent-contrast text-sm hover:font-bold w-fit text-left" 237 onMouseDown={() => { 238 props.setMenuState("domain"); 239 }} 240 > 241 Edit custom domain 242 </button> 243 ) : ( 244 <MenuItem 245 className="font-normal text-tertiary text-sm" 246 onSelect={(e) => { 247 e.preventDefault(); 248 props.setMenuState("domain"); 249 }} 250 > 251 Publish on a custom domain 252 </MenuItem> 253 )} 254 </> 255 ); 256};